﻿using DotnetCore.Extension.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace DotnetCore.Module
{
    public class DcModuleManager : IDcModuleManager
    {

        private DcModuleManager()
        {

        }
        private static DcModuleManager _instance { get; set; }
        public static DcModuleManager Get()
        {
            return _instance ?? (_instance = new DcModuleManager());
        }

        private DcModuleCollection _modules;

        public IReadOnlyList<DcModuleInfo> Modules => _modules.AsReadOnly();

        public void Initialize(Type startupModule)
        {
            _modules = new DcModuleCollection(startupModule);
            LoadAllModules();
        }

        public void StartModule()
        {
            _modules.ForEach(module => module.Instance.PreInitialize());
            _modules.ForEach(module => module.Instance.Initialize());
            _modules.ForEach(module => module.Instance.PostInitialize());
        }

        private void LoadAllModules()
        {
            var modules = DcModule.FindDependedModuleTypesRecursivelyIncludingGivenModule(_modules.StartupModuleType);

            foreach (var moduleType in modules)
            {
                var moduleObject = (DcModule)moduleType.Assembly.CreateInstance(moduleType.FullName);
                var moduleInfo = new DcModuleInfo(moduleType, moduleObject);
                _modules.Add(moduleInfo);
            }
            DcModuleCollection.EnsureStartupModuleToBeLast(_modules, _modules.StartupModuleType);
            
        }
        
        

        public void Register(IServiceCollection services)
        {
            _modules.ForEach(module =>
            {
                
                SetDependency<ITransientDependency>(services, module.Type.Assembly, (t1, t2) =>
                 {
                     if (t2 == null)
                     {
                         services.AddTransient(t1);
                     }
                     else
                     {
                         services.AddTransient(t1, t2);
                     }
                 });
                SetDependency<ISingletonDependency>(services, module.Type.Assembly, (t1, t2) =>
                {
                    if (t2 == null)
                    {
                        services.AddSingleton(t1);
                    }
                    else
                    {
                        services.AddSingleton(t1, t2);
                    }
                });
            });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="IDependency">依赖注入类型</typeparam>
        /// <param name="services"></param>
        /// <param name="assembly"></param>
        /// <param name="action"></param>
        private void SetDependency<IDependency>(IServiceCollection services, Assembly assembly,
            //t1 接口、类 
            //t2 接口
            Action<Type, Type> action)
        {
            var transientTypes = GetTypesFromAssembly<IDependency>(assembly);
            var transientImps = transientTypes
               .Where(t => !t.IsInterface)
               .ToList();
            foreach (var imp in transientImps)
            {
                var interfaces = imp.GetInterfaces()
                .Where(i => i.GetInterface(typeof(IDependency).FullName) != null);
                if (interfaces.Count() == 0)
                {
                    action(imp, null);
                }
                foreach (var i in interfaces)
                {
                    action(i, imp);
                }
            }
        }

        private void SetTransientDependency(IServiceCollection services, DcModuleInfo module)
        {
            var transientTypes = GetTypesFromAssembly<ITransientDependency>(module.Type.Assembly);
            var transientImps = transientTypes
               .Where(t => !t.IsInterface)
               .ToList();
            foreach (var imp in transientImps)
            {
                var interfaces = imp.GetInterfaces()
                .Where(i => i.GetInterface(typeof(ITransientDependency).FullName) != null);
                if (interfaces.Count() == 0)
                {
                    services.AddTransient(imp);
                }
                foreach (var i in interfaces)
                {
                    services.AddTransient(i, imp);
                }
            }
        }

        public List<TypeInfo> GetTypesFromAssembly<T>(Assembly assembly)
        {
            return assembly.DefinedTypes
                    .Where(t => t.GetInterfaces().Any(i => i == typeof(T)))
                    .ToList();
        }
    }
}
